"""Enhanced Contact Sheet Generator - Professional Image Portfolio and Catalog Tool
This script creates JPG contact sheets from images in a selected folder,
formatted for photo printing services with 4 columns and 5 rows.

Copyright (c) 2025 Chatbizdb.com - Yahya Nazer
All rights reserved.

File: 01-Generate-Pdf-Html-Contact-Sheet_V01.py
Version: 1.0.0
Last Modified: 2025-07-06
Python Version: 3.6+

Features:
- Cross-platform compatibility (Windows, macOS, Linux)
- JPG contact sheets for photo printing (8x10 inches at 300 DPI)
- Support for multiple image formatFreports (JPG, JPEG, PNG, BMP, TIFF, GIF, WEBP)
- 4 columns x 5 rows layout (20 images per sheet)
- Professional layout with titles and page numbers
- Interactive HTML gallery
- Responsive design with hover effects

Updated behavior:
- User selects an image folder (e.g. TOP/Data).
- TOP is taken as the parent folder of the selected folder.
- All outputs are stored under: TOP/report/
    - JPG sheets:   TOP/report/report-contact-sheet-jpg-<timestamp>/
    - HTML gallery: TOP/report/report-contact-sheet-html-<timestamp>/Contacts-<timestamp>.html
"""

__version__ = '1.0.0'
__author__ = 'Yahya Nazer'
__copyright__ = 'Copyright (c) 2025 Chatbizdb.com - Yahya Nazer'
__license__ = 'Proprietary'
__maintainer__ = 'Yahya Nazer'
__email__ = 'contact@chatbizdb.com'
__status__ = 'Production'
__file_path__ = __file__

print_flag = False

import os
import sys
import io
import subprocess
from datetime import datetime
from tkinter import filedialog, Tk
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont

if sys.platform.startswith('win'):
    sys.stdout.reconfigure(encoding='utf-8')
    sys.stderr.reconfigure(encoding='utf-8')

Image.MAX_IMAGE_PIXELS = None
import tempfile


def debug_print(message):
    if print_flag:
        print(message)


def select_folder():
    debug_print('DEBUG: Opening folder selection dialog...')
    root = Tk()
    root.withdraw()
    folder_path = filedialog.askdirectory(title='Select folder with images')
    root.destroy()
    if folder_path:
        debug_print(f'DEBUG: User selected folder: {folder_path}')
        debug_print(f'DEBUG: Folder exists: {os.path.exists(folder_path)}')
        debug_print(f'DEBUG: Folder is directory: {os.path.isdir(folder_path)}')
    else:
        debug_print('DEBUG: No folder selected by user')
    return folder_path


def get_image_files(folder_path):
    debug_print(f'DEBUG: Starting image file scan in: {folder_path}')
    image_extensions = {
        '.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.tif',
        '.gif', '.webp'
    }
    image_files = []
    excluded_files = []
    processed_dirs = []

    for root, dirs, files in os.walk(folder_path):
        processed_dirs.append(root)
        debug_print(f'DEBUG: Scanning directory: {root}')
        debug_print(f'DEBUG: Found {len(files)} files in current directory')
        for file in files:
            file_path = os.path.join(root, file)
            if file.startswith('._'):
                excluded_files.append(file)
                debug_print(f'DEBUG: Excluding system file: {file}')
                continue
            file_ext = os.path.splitext(file)[1].lower()
            if file_ext in image_extensions:
                image_files.append(file_path)
                debug_print(f'DEBUG: Added image file: {file} ({file_ext})')
            else:
                debug_print(f'DEBUG: Skipping non-image file: {file} ({file_ext})')

    debug_print('DEBUG: Scan complete')
    debug_print(f'DEBUG: Processed {len(processed_dirs)} directories')
    debug_print(f'DEBUG: Found {len(image_files)} valid image files')
    debug_print(f'DEBUG: Excluded {len(excluded_files)} system files')
    debug_print(f'DEBUG: Supported extensions: {sorted(image_extensions)}')
    return sorted(image_files)


def create_thumbnail(image_path, size=(200, 200)):
    filename = os.path.basename(image_path)
    debug_print(f'DEBUG: Creating thumbnail for: {filename}')
    try:
        with Image.open(image_path) as img:
            original_size = (img.width, img.height)
            pixels = img.width * img.height
            debug_print(f'DEBUG: Original image size: {original_size} ({pixels:,} pixels)')
            if pixels > 100000000:
                print(f'WARNING: Processing very large image: {filename} ({img.width}x{img.height})')
            debug_print(f'DEBUG: Original image mode: {img.mode}')
            if img.mode in ('RGBA', 'LA', 'P'):
                debug_print(f'DEBUG: Converting image mode from {img.mode} to RGB')
                img = img.convert('RGB')
            if pixels > 50000000:
                debug_print('DEBUG: Pre-resizing large image before thumbnail creation')
                max_dimension = 2000
                if img.width > max_dimension or img.height > max_dimension:
                    old_size = (img.width, img.height)
                    img.thumbnail((max_dimension, max_dimension), Image.Resampling.LANCZOS)
                    debug_print(f'DEBUG: Pre-resized from {old_size} to {(img.width, img.height)}')
            debug_print(f'DEBUG: Creating thumbnail with target size: {size}')
            img.thumbnail(size, Image.Resampling.LANCZOS)
            thumbnail_size = (img.width, img.height)
            debug_print(f'DEBUG: Thumbnail created with size: {thumbnail_size}')
            thumb = Image.new('RGB', size, 'white')
            debug_print(f'DEBUG: Created white background canvas: {size}')
            x = (size[0] - img.width) // 2
            y = (size[1] - img.height) // 2
            thumb.paste(img, (x, y))
            debug_print(f'DEBUG: Positioned thumbnail at offset: ({x}, {y})')
            debug_print(f'DEBUG: Successfully created thumbnail for: {filename}')
            return thumb
    except Exception as e:
        print(f'ERROR: Failed to create thumbnail for {filename}: {e}')
        debug_print('DEBUG: Creating error placeholder thumbnail')
        thumb = Image.new('RGB', size, 'lightgray')
        draw = ImageDraw.Draw(thumb)
        draw.text((10, size[1] // 2), 'Error loading\nimage', fill='black')
        debug_print(f'DEBUG: Created error placeholder for: {filename}')
        return thumb


def get_image_info(image_path):
    filename = os.path.basename(image_path)
    debug_print(f'DEBUG: Extracting image info for: {filename}')
    try:
        with Image.open(image_path) as img:
            file_size = os.path.getsize(image_path)
            file_size_mb = file_size / (1024 * 1024)
            info = {
                'filename': filename,
                'dimensions': f'{img.width} x {img.height}',
                'mode': img.mode,
                'size_mb': f'{file_size_mb:.2f} MB'
            }
            debug_print(
                f"DEBUG: Image info extracted: {info['dimensions']}, "
                f"{info['mode']}, {info['size_mb']}"
            )
            return info
    except Exception as e:
        print(f'ERROR: Failed to extract image info for {filename}: {e}')
        info = {
            'filename': filename,
            'dimensions': 'Unknown',
            'mode': 'Unknown',
            'size_mb': 'Unknown'
        }
        debug_print(f'DEBUG: Using fallback info for: {filename}')
        return info


def create_contact_sheet_jpg(image_files, output_dir, images_per_row=4, images_per_page=20):
    debug_print('DEBUG: Starting JPG contact sheet creation')
    debug_print(f'DEBUG: Output directory: {output_dir}')
    debug_print(f'DEBUG: Images per row: {images_per_row}')
    debug_print(f'DEBUG: Images per page: {images_per_page}')
    debug_print(f'DEBUG: Total images to process: {len(image_files)}')

    sheet_width = 2400
    sheet_height = 3000
    margin = 60
    available_width = sheet_width - margin * 2
    available_height = sheet_height - margin * 3
    cell_width = available_width // images_per_row
    cell_height = (available_height - 100) // 5
    img_size = min(cell_width - 20, cell_height - 60)

    debug_print(f'DEBUG: Sheet dimensions: {sheet_width}x{sheet_height}')
    debug_print(f'DEBUG: Cell dimensions: {cell_width}x{cell_height}')
    debug_print(f'DEBUG: Image size: {img_size}x{img_size}')

    sheet_count = 0

    for batch_start in range(0, len(image_files), images_per_page):
        sheet_count += 1
        batch_images = image_files[batch_start:batch_start + images_per_page]
        debug_print(f'DEBUG: Creating sheet {sheet_count} with {len(batch_images)} images')

        contact_sheet = Image.new('RGB', (sheet_width, sheet_height), 'white')
        draw = ImageDraw.Draw(contact_sheet)

        try:
            font_title = ImageFont.truetype('arial.ttf', 36)
            font_filename = ImageFont.truetype('arial.ttf', 24)
        except Exception:
            try:
                font_title = ImageFont.load_default()
                font_filename = ImageFont.load_default()
            except Exception:
                font_title = None
                font_filename = None

        timestamp = datetime.now().strftime('%Y-%m-%d %H:%M')
        title_text = f'Contact Sheet {sheet_count} - Generated {timestamp}'
        if font_title:
            title_bbox = draw.textbbox((0, 0), title_text, font=font_title)
            title_width = title_bbox[2] - title_bbox[0]
            title_x = (sheet_width - title_width) // 2
            draw.text((title_x, 20), title_text, fill='black', font=font_title)
        else:
            draw.text((margin, 20), title_text, fill='black')
        debug_print(f'DEBUG: Added title: {title_text}')

        for i, img_path in enumerate(batch_images):
            row = i // images_per_row
            col = i % images_per_row
            x = margin + col * cell_width + (cell_width - img_size) // 2
            y = margin + 100 + row * cell_height + 10
            img_filename = os.path.basename(img_path)
            debug_print(
                f'DEBUG: Processing image {i + 1}/{len(batch_images)}: '
                f'{img_filename} at position ({row}, {col})'
            )
            try:
                thumbnail = create_thumbnail(img_path, (img_size, img_size))
                contact_sheet.paste(thumbnail, (x, y))
                filename_y = y + img_size + 5
                filename_text = img_filename
                if len(filename_text) > 25:
                    filename_text = filename_text[:22] + '...'
                if font_filename:
                    text_bbox = draw.textbbox((0, 0), filename_text, font=font_filename)
                    text_width = text_bbox[2] - text_bbox[0]
                    text_x = x + (img_size - text_width) // 2
                    draw.text((text_x, filename_y), filename_text, fill='black', font=font_filename)
                else:
                    draw.text((x, filename_y), filename_text, fill='black')
                debug_print(f'DEBUG: Added {img_filename} at position ({x}, {y})')
            except Exception as e:
                print(f'ERROR: Failed to process {img_filename}: {e}')
                draw.rectangle([x, y, x + img_size, y + img_size],
                               fill='lightgray', outline='gray')
                draw.text((x + 10, y + img_size // 2),
                          'Error\nLoading\nImage', fill='red')

        page_info = (
            f'Page {sheet_count} - Images {batch_start + 1} to '
            f'{min(batch_start + images_per_page, len(image_files))} of {len(image_files)}'
        )
        if font_filename:
            info_bbox = draw.textbbox((0, 0), page_info, font=font_filename)
            info_width = info_bbox[2] - info_bbox[0]
            info_x = (sheet_width - info_width) // 2
            draw.text((info_x, sheet_height - 40), page_info,
                      fill='gray', font=font_filename)
        else:
            draw.text((margin, sheet_height - 40), page_info, fill='gray')

        sheet_filename = f'contact_sheet_{sheet_count:02d}.jpg'
        os.makedirs(output_dir, exist_ok=True)
        sheet_path = os.path.join(output_dir, sheet_filename)
        contact_sheet.save(sheet_path, 'JPEG', quality=95, dpi=(300, 300))
        print(f'SUCCESS: Created {sheet_filename} ({len(batch_images)} images)')
        debug_print(f'DEBUG: Saved contact sheet: {sheet_path}')
        debug_print(f'DEBUG: File size: {os.path.getsize(sheet_path)} bytes')

    return sheet_count


def create_html_contact_sheet(image_files, output_path, images_per_row=4):
    debug_print('DEBUG: Starting HTML contact sheet creation')
    debug_print(f'DEBUG: Output path: {output_path}')
    debug_print(f'DEBUG: Images per row: {images_per_row}')
    debug_print(f'DEBUG: Total images to process: {len(image_files)}')

    source_folder = os.path.dirname(image_files[0]) if image_files else 'Unknown'
    timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S')

    html_content = f"""<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Image Contact Sheet</title>
    <style>
        body {{
            font-family: Arial, sans-serif;
            margin: 20px;
            background-color: #f5f5f5;
        }}
        .header {{
            text-align: center;
            margin-bottom: 30px;
            background: white;
            padding: 20px;
            border-radius: 10px;
            box-shadow: 0 2px 5px rgba(0,0,0,0.1);
        }}
        .header h1 {{
            margin: 0;
            color: #333;
        }}
        .header p {{
            margin: 5px 0;
            color: #666;
        }}
        .gallery {{
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
            gap: 20px;
            max-width: 1200px;
            margin: 0 auto;
        }}
        .image-card {{
            background: white;
            border-radius: 10px;
            overflow: hidden;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
            transition: transform 0.2s ease, box-shadow 0.2s ease;
            cursor: pointer;
        }}
        .image-card:hover {{
            transform: translateY(-5px);
            box-shadow: 0 5px 20px rgba(0,0,0,0.2);
        }}
        .image-container {{
            position: relative;
            width: 100%;
            height: 200px;
            overflow: hidden;
            background-color: #f0f0f0;
        }}
        .thumbnail {{
            width: 100%;
            height: 100%;
            object-fit: contain;
            transition: transform 0.2s ease;
        }}
        .image-card:hover .thumbnail {{
            transform: scale(1.05);
        }}
        .image-info {{
            padding: 15px;
        }}
        .filename {{
            font-weight: bold;
            color: #333;
            margin-bottom: 5px;
            word-break: break-word;
        }}
        .details {{
            font-size: 12px;
            color: #666;
            line-height: 1.4;
        }}
        .open-btn {{
            display: inline-block;
            margin-top: 8px;
            padding: 5px 10px;
            background-color: #007bff;
            color: white;
            text-decoration: none;
            border-radius: 3px;
            font-size: 11px;
            transition: background-color 0.2s ease;
        }}
        .open-btn:hover {{
            background-color: #0056b3;
        }}
        .stats {{
            text-align: center;
            margin-top: 30px;
            padding: 15px;
            background: white;
            border-radius: 10px;
            box-shadow: 0 2px 5px rgba(0,0,0,0.1);
        }}
        @media (max-width: 768px) {{
            .gallery {{
                grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
                gap: 15px;
            }}
            body {{
                margin: 10px;
            }}
        }}
    </style>
</head>
<body>
    <div class="header">
        <h1>Image Contact Sheet</h1>
        <p>Generated: {timestamp}</p>
        <p>Source Folder: {source_folder}</p>
    </div>

    <div class="gallery">
"""

    thumb_dir = os.path.join(os.path.dirname(output_path), 'thumbnails')
    os.makedirs(thumb_dir, exist_ok=True)
    debug_print(f'DEBUG: Created thumbnails directory: {thumb_dir}')

    successful_images = 0
    failed_images = 0

    for i, img_path in enumerate(image_files):
        img_filename = os.path.basename(img_path)
        debug_print(f'DEBUG: Processing HTML image {i + 1}/{len(image_files)}: {img_filename}')
        try:
            thumbnail = create_thumbnail(img_path, (250, 200))
            thumb_filename = f'thumb_{i:04d}.jpg'
            thumb_path = os.path.join(thumb_dir, thumb_filename)
            thumbnail.save(thumb_path, 'JPEG', quality=85)
            debug_print(f'DEBUG: Saved HTML thumbnail: {thumb_filename}')
            info = get_image_info(img_path)
            file_url = f"file:///{img_path.replace(os.sep, '/')}"
            thumb_rel_path = f'thumbnails/{thumb_filename}'
            html_content += f"""
        <div class="image-card" onclick="window.open('{file_url}', '_blank')">
            <div class="image-container">
                <img src="{thumb_rel_path}" alt="{info['filename']}" class="thumbnail">
            </div>
            <div class="image-info">
                <div class="filename">{info['filename']}</div>
                <div class="details">
                    Dimensions: {info['dimensions']}<br>
                    Size: {info['size_mb']}<br>
                    Format: {info['mode']}
                </div>
                <a href="{file_url}" class="open-btn" onclick="event.stopPropagation()">Open Image</a>
            </div>
        </div>
"""
            successful_images += 1
        except Exception as e:
            print(f'ERROR: Failed to process {img_filename} for HTML: {e}')
            failed_images += 1

    debug_print(
        f'DEBUG: HTML processing complete - Success: {successful_images}, Failed: {failed_images}'
    )

    html_content += f"""
    </div>

    <div class="stats">
        <h3>Contact Sheet Statistics</h3>
        <p>Total Images: {len(image_files)}</p>
        <p>Successfully Processed: {successful_images}</p>
        <p>Generated on: {timestamp}</p>
    </div>

    <script>
        document.querySelectorAll('.image-card').forEach(card => {{
            card.addEventListener('click', function() {{
                this.style.transform = 'scale(0.98)';
                setTimeout(() => {{
                    this.style.transform = '';
                }}, 100);
            }});
        }});
    </script>
</body>
</html>
"""

    with open(output_path, 'w', encoding='utf-8') as f:
        f.write(html_content)

    debug_print(f'DEBUG: HTML contact sheet created: {output_path}')
    debug_print(f'DEBUG: HTML file size: {os.path.getsize(output_path)} bytes')
    debug_print(f'DEBUG: Thumbnails folder: {thumb_dir}')


def main():
    print(f'Enhanced Contact Sheet Generator v{__version__}')
    print('Professional Image Portfolio and Catalog Tool')
    print('=' * 60)
    print()

    print('STEP 1: Folder Selection')
    print('-' * 25)
    folder_path = select_folder()
    print(f'DEBUG: Folder selection returned: {folder_path}')
    if not folder_path:
        print('ERROR: No folder selected. Exiting application.')
        return

    print(f'SUCCESS: Selected folder: {folder_path}')
    print()

    print('STEP 2: Image File Discovery')
    print('-' * 30)
    image_files = get_image_files(folder_path)
    if not image_files:
        print('ERROR: No image files found in the selected folder.')
        return
    print(f'SUCCESS: Found {len(image_files)} image files')
    print()

    print('STEP 3: Setting Up Output Directories')
    print('-' * 37)

    # TOP folder = parent of selected folder
    images_folder = Path(folder_path)
    top_folder = images_folder.parent
    print(f'SUCCESS: Top folder detected: {top_folder}')

    # TOP/report
    report_root = top_folder / 'report'
    try:
        report_root.mkdir(parents=True, exist_ok=True)
        print(f'SUCCESS: Report root folder ready: {report_root}')
    except Exception as e:
        print(f'ERROR: Could not create report root directory: {e}')
        print('FALLBACK: Using selected folder as report root')
        report_root = images_folder

    timestamp = datetime.now().strftime('%Y-%m-%d--%H-%M')

    # JPG contact sheets folder: TOP/report/report-contact-sheet-jpg-<timestamp>
    contact_sheets_dir = report_root / f'report-contact-sheet-jpg-{timestamp}'
    try:
        contact_sheets_dir.mkdir(parents=True, exist_ok=True)
        print(f'SUCCESS: JPG contact sheets folder: {contact_sheets_dir}')
    except Exception as e:
        print(f'ERROR: Could not create JPG contact sheets directory: {e}')
        print('FALLBACK: Using report root')
        contact_sheets_dir = report_root

    # HTML report folder: TOP/report/report-contact-sheet-html-<timestamp>
    html_root_dir = report_root / f'report-contact-sheet-html-{timestamp}'
    try:
        html_root_dir.mkdir(parents=True, exist_ok=True)
        print(f'SUCCESS: HTML report folder: {html_root_dir}')
    except Exception as e:
        print(f'ERROR: Could not create HTML report directory: {e}')
        print('FALLBACK: Using report root for HTML')
        html_root_dir = report_root

    html_filename = f'Contacts-{timestamp}.html'
    html_path = html_root_dir / html_filename

    print()
    try:
        print('STEP 4A: Creating JPG Contact Sheets')
        print('-' * 35)
        sheet_count = create_contact_sheet_jpg(image_files, str(contact_sheets_dir))
        print(f'SUCCESS: Created {sheet_count} contact sheet(s)')
        print()

        print('STEP 4B: Creating HTML Gallery')
        print('-' * 29)
        create_html_contact_sheet(image_files, str(html_path))
        print('SUCCESS: HTML gallery created')
        print()

        print('STEP 5: Opening Generated Files')
        print('-' * 31)

        # Open folders and HTML
        try:
            if sys.platform.startswith('win'):
                os.startfile(str(contact_sheets_dir))
                os.startfile(str(html_path))
            elif sys.platform.startswith('darwin'):
                subprocess.call(['open', str(contact_sheets_dir)])
                subprocess.call(['open', str(html_path)])
            else:
                subprocess.call(['xdg-open', str(contact_sheets_dir)])
                subprocess.call(['xdg-open', str(html_path)])
            print('SUCCESS: Files opened successfully!')
        except Exception as e:
            print(f'WARNING: Could not auto-open files: {e}')

        print()
        print('GENERATED FILES:')
        print(f'- JPG Contact Sheets: {contact_sheets_dir}')
        print(f'- HTML Gallery: {html_path}')
        print('- Format: 8x10 inch JPG at 300 DPI for photo printing')
    except Exception as e:
        print(f'ERROR: Failed to create contact sheets or HTML: {e}')


if __name__ == '__main__':
    print(f'Enhanced Contact Sheet Generator v{__version__}')
    print(f'Created by {__author__}')
    print(f'{__copyright__}')
    print()

    missing_deps = []
    check_mark = '[OK]' if sys.platform.startswith('win') else '✓'
    cross_mark = '[X]' if sys.platform.startswith('win') else '✗'

    try:
        import tkinter  # noqa
        print(f'{check_mark} tkinter - Available')
    except ImportError:
        missing_deps.append('tkinter')
        print(f'{cross_mark} tkinter - MISSING')

    try:
        from PIL import Image as _TestImage  # noqa
        print(f'{check_mark} Pillow (PIL) - Available')
    except ImportError:
        missing_deps.append('Pillow')
        print(f'{cross_mark} Pillow (PIL) - MISSING')

    if missing_deps:
        print()
        print('ERROR: Missing required dependencies!')
        print('Please install missing packages:')
        for dep in missing_deps:
            if dep == 'Pillow':
                print(f'  pip install {dep}')
            else:
                print(f'  {dep} should be included with Python')
        print()
        input('Press Enter to exit...')
        sys.exit(1)

    print()
    print('All dependencies satisfied. Starting application...')
    print()
    main()
